package com.huidu.bitrade.service;


import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson2.JSON;
import com.huidu.bitrade.constant.AdvertiseType;
import com.huidu.bitrade.constant.OrderStatus;
import com.huidu.bitrade.constant.PageModel;
import com.huidu.bitrade.constant.SystemConfigConstants;
import com.huidu.bitrade.dao.OrderDao;
import com.huidu.bitrade.entity.*;
import com.huidu.bitrade.exception.InformationExpiredException;
import com.huidu.bitrade.exception.ServiceException;
import com.huidu.bitrade.pagination.Criteria;
import com.huidu.bitrade.pagination.PageResult;
import com.huidu.bitrade.pagination.Restrictions;
import com.huidu.bitrade.service.Base.BaseService;
import com.huidu.bitrade.util.IdWorkByTwitter;
import com.huidu.bitrade.util.MessageResult;
import com.huidu.bitrade.vo.OtcOrderVO;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQuery;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.SQLQuery;
import org.hibernate.transform.Transformers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.validation.constraints.NotNull;

import static com.huidu.bitrade.util.BigDecimalUtils.add;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * @author QQ：97095646
 * @date 2020年12月11日
 */
@Service
@Slf4j
public class OrderService extends BaseService {
    @Autowired
    private LocaleMessageSourceService msService;

    @PersistenceContext
    private EntityManager em;

    @Autowired
    private OrderDao orderDao;


    @Autowired
    private IdWorkByTwitter idWorkByTwitter;
    @Autowired
    private AdvertiseService advertiseService;
    @Autowired
    private MemberWalletService memberWalletService;
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private SystemConfigService systemConfigService;


    @Transactional(rollbackFor = Exception.class)
    public void cancelOrderTask(Order order) throws InformationExpiredException {
//        if (order.getAdvertiseType().equals(AdvertiseType.BUY)) {
//            //更改广告
//            if (!advertiseService.updateAdvertiseAmountForCancel(order.getAdvertiseId(), order.getNumber())) {
//                throw new InformationExpiredException("Information Expired");
//            }
//        } else {
//            //更改广告
//            if (!advertiseService.updateAdvertiseAmountForCancel(order.getAdvertiseId(), BigDecimalUtils.add(order.getNumber(), order.getCommission()))) {
//                throw new InformationExpiredException("Information Expired");
//            }
//        }
        BigDecimal number = order.getNumber();
        Long memberId;
        if (order.getAdvertiseType().equals(AdvertiseType.BUY)) {
            //如果是买入广告，用户是卖出，商家是买入，取消订单需要返还用户冻结
            number = add(order.getNumber(), order.getCommission());
            memberId = order.getCustomerId();
            MemberWallet memberWallet = memberWalletService.findByOtcCoinAndMemberId(order.getCoin(), memberId);
            BigDecimal a=memberWalletService.selectBalance(memberWallet);//解冻钱包之前的余额
            log.error("用户:"+memberId+" 钱包:"+memberWallet.getId()+" ctc取消订单前余额:"+a);
            MessageResult result = memberWalletService.thawBalance(memberWallet, order.getNumber());
            BigDecimal b=memberWalletService.selectBalance(memberWallet);//解冻钱包之后的余额
            log.error("用户:"+memberId+" 钱包:"+memberWallet.getId()+" ctc取消订单后余额:"+b);
            log.error("ctc取消订单结果"+result.getCode());
            //解冻后的余额大于解冻前的余额  证明解冻成功
            if(result.getCode() != 0){
                throw new InformationExpiredException("Information Expired");
            }
        }
        if (!advertiseService.updateAdvertiseAmountForCancel(order.getAdvertiseId(), number)) {
            throw new InformationExpiredException("Information Expired");
        }
        //取消订单
        if (!(this.cancelOrder(order.getOrderSn()) > 0)) {
            throw new InformationExpiredException("Information Expired");
        }
    }

    /**
     * 条件查询对象 pageNo pageSize 同时传时分页
     *
     * @param predicateList
     * @param pageNo
     * @param pageSize
     * @return
     */
    @Transactional(readOnly = true)
    public PageResult<Order> query(List<Predicate> predicateList, Integer pageNo, Integer pageSize) {
        List<Order> list;
        JPAQuery<Order> jpaQuery = queryFactory.selectFrom(QOrder.order);
        if (predicateList != null) {
            jpaQuery.where(predicateList.toArray(new Predicate[predicateList.size()]));
        }
        if (pageNo != null && pageSize != null) {
            list = jpaQuery.offset((pageNo - 1) * pageSize).limit(pageSize).fetch();
        } else {
            list = jpaQuery.fetch();
        }
        return new PageResult<>(list, jpaQuery.fetchCount());
    }

    public Order findOne(Long id) {
        return orderDao.findById(id).orElse(null);
    }

    public Order findOneByOrderSn(String orderSn) {
        return orderDao.getOrderByOrderSn(orderSn);
    }

    public int updateOrderAppeal(String orderSn) {
        return orderDao.updateAppealOrder(OrderStatus.APPEAL, orderSn);
    }

    public int payForOrder(String orderSn) {
        return orderDao.updatePayOrder(new Date(), OrderStatus.PAID, orderSn);
    }
    /**
     * 更新支付凭证
     *
     * @param orderSn
     * @return
     */
    public int payDocumentOrder(String orderSn,String payDocument) {
        return orderDao.updatePayDocumentOrder( payDocument,orderSn);
    }
    /**
     * 取消订单
     *
     * @param orderSn
     * @return
     */
    public int cancelOrder(String orderSn) {
        return orderDao.cancelOrder(new Date(), OrderStatus.CANCELLED, orderSn);
    }

    /**
     * 订单放行
     *
     * @param orderSn
     * @return
     */
    public int releaseOrder(String orderSn) {
        return orderDao.releaseOrder(new Date(), OrderStatus.COMPLETED, orderSn);
    }

    /**
     * 生成订单
     *
     * @param order
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public Order saveOrder(Order order) {
        order.setOrderSn(String.valueOf(idWorkByTwitter.nextId()));
        return orderDao.save(order);
    }

    public Page<Order> pageQuery(int pageNo, Integer pageSize, OrderStatus status, long id, String orderSn) {
        Sort orders = Criteria.sortStatic("id.desc");
        PageRequest pageRequest = PageRequest.of(pageNo, pageSize, orders);
        Criteria<Order> specification = new Criteria<Order>();
        specification.add(Restrictions.or(Restrictions.eq("memberId", id, false), Restrictions.eq("customerId", id, false)));
        specification.add(Restrictions.eq("status", status, false));
        if (StringUtils.isNotBlank(orderSn)) {
            specification.add(Restrictions.like("orderSn", orderSn, false));
        }
        return orderDao.findAll(specification, pageRequest);
    }


    public Map getOrderBySn(Long memberId, String orderSn) {
        String sql = "select o.*,m.real_name from otc_order o  join member m on o.customer_id=m.id and o.member_id=:memberId and o.order_sn =:orderSn ";
        Query query = em.createNativeQuery(sql);
        //设置结果转成Map类型
        query.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
        Object object = query.setParameter("memberId", memberId).setParameter("orderSn", orderSn).getSingleResult();
        Map map = (HashMap) object;
        return map;
    }

    public List<Order> checkExpiredOrder() {
        return orderDao.findAllExpiredOrder(new Date());
    }


    @Override
    public List<Order> findAll() {
        return orderDao.findAll();
    }

    public Order save(Order order) {
        return orderDao.save(order);
    }

    public MessageResult getOrderNum() {
        Predicate predicate = QOrder.order.status.eq(OrderStatus.NONPAYMENT);
        Long noPayNum = orderDao.count(predicate);
        Long paidNum = orderDao.count(QOrder.order.status.eq(OrderStatus.PAID));
        Long finishedNum = orderDao.count(QOrder.order.status.eq(OrderStatus.COMPLETED));
        Long cancelNum = orderDao.count(QOrder.order.status.eq(OrderStatus.CANCELLED));
        Long appealNum = orderDao.count(QOrder.order.status.eq(OrderStatus.APPEAL));
        Map<String, Long> map = new HashMap<>();
        map.put("noPayNum", noPayNum);
        map.put("paidNum", paidNum);
        map.put("finishedNum", finishedNum);
        map.put("cancelNum", cancelNum);
        map.put("appealNum", appealNum);
        return MessageResult.getSuccessInstance(msService.getMessage("GET_SUCCESS"), map);
    }

    public List<Order> getAllOrdering(Long id) {
        return orderDao.fingAllProcessingOrder(id, OrderStatus.APPEAL, OrderStatus.PAID, OrderStatus.NONPAYMENT);
    }

    public long findMemberOrdering(Long id) {
        return orderDao.findMemberOrdering(id);
    }
    public long findAllProcessingUnitOrder(Long memberId,String unit) {
        return orderDao.findAllProcessingUnitOrder(memberId, OrderStatus.APPEAL, OrderStatus.PAID, OrderStatus.NONPAYMENT,unit);
    }

    public Order findOneByOrderId(String orderId) {
        return orderDao.getOrderByOrderSn(orderId);
    }
    public Page<Order> findAll(com.querydsl.core.types.Predicate predicate, Pageable pageable) {
        return orderDao.findAll(predicate, pageable);
    }

    public Page<OtcOrderVO> outExcel(List<Predicate> predicates , PageModel pageModel){
        List<OrderSpecifier> orderSpecifiers = pageModel.getOrderSpecifiers();
        JPAQuery<OtcOrderVO> query = queryFactory.select(
                Projections.fields(OtcOrderVO.class,
                        QOrder.order.id.as("id"),
                        QOrder.order.orderSn.as("orderSn"),
                        QOrder.order.advertiseType.as("advertiseType"),
                        QOrder.order.createTime.as("createTime"),
                        QOrder.order.memberName.as("memberName"),
                        QOrder.order.customerName.as("customerName"),
                        QOrder.order.coin.unit,
                        QOrder.order.money,
                        QOrder.order.number,
                        QOrder.order.commission.as("fee"),
                        QOrder.order.payMode.as("payMode"),
                        QOrder.order.releaseTime.as("releaseTime"),
                        QOrder.order.cancelTime.as("cancelTime"),
                        QOrder.order.payTime.as("payTime"),
                        QOrder.order.payDocument.as("payDocument"),
                        QOrder.order.status.as("status"))
        ).from(QOrder.order).where(predicates.toArray(new BooleanExpression[predicates.size()]));
        query.orderBy(orderSpecifiers.toArray(new OrderSpecifier[orderSpecifiers.size()]));
        List<OtcOrderVO> list = query.offset((pageModel.getPageNo()-1)*pageModel.getPageSize()).limit(pageModel.getPageSize()).fetch();
        long total = query.fetchCount() ;
        return new PageImpl<>(list,pageModel.getPageable(),total);
    }

    public List<Object[]> getOtcOrderStatistics(String date){
        return orderDao.getOtcTurnoverAmount(date);
    }

    /**
     * 获取otc商家当天买入额度
     * @return
     */
    public BigDecimal getMerchantDealPriceDay(Long memberId){
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String start = formatter.format(LocalDateTime.of(LocalDate.now(), LocalTime.MIN));
        String end = formatter.format(LocalDateTime.of(LocalDate.now(), LocalTime.MAX));
        return orderDao.getMerchantDealPriceDay(memberId,start,end);
    }

    /**
     * 获取用户当天交易额
     * @return
     */
    public BigDecimal getMemberSellDealPriceDay(Long memberId){
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String start = formatter.format(LocalDateTime.of(LocalDate.now(), LocalTime.MIN));
        String end = formatter.format(LocalDateTime.of(LocalDate.now(), LocalTime.MAX));
        return orderDao.getMemberSellDealPriceDay(memberId,start,end);
    }

    /**
     * 商家是否交易上限
     * @return
     */
    public Boolean isDealAstrict(Member member,BigDecimal money){
        BigDecimal price = getMerchantDealPriceDay(member.getId());
        log.info("========已下单金额======{}",price);
        price = money.add(price);
        BigDecimal value = BigDecimal.ZERO;
        //判断是否为新商家
        if(Objects.nonNull(member.getBusinessDepositStrategy())){
            //获取
            value = member.getBusinessDepositStrategy().getDailyAmount();
        }else{
            value = getOtcDealPrice(SystemConfigConstants.OTC_DAY_DEAL_PRICE);
        }
        if(Objects.isNull(value)){
            return Boolean.FALSE;
        }
        if (price.compareTo(value)>0) {
            return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }

    /**
     * 判断商家出金是否大于入金
     * @return
     */
    public Boolean isForbid(Member member){
        //查询商家历史交易订单出金
        BigDecimal sell = orderDao.getBusinessPrice(AdvertiseType.SELL.getOrdinal(),member.getId(),member.getBusinessIsForbidStartTime());
        BigDecimal buy = orderDao.getBusinessPrice(AdvertiseType.BUY.getOrdinal(),member.getId(),member.getBusinessIsForbidStartTime());
        if(sell.compareTo(buy)>0){
            return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }

    /**
     * 用户出金是否上限
     * @return
     */
    public Boolean isMemberDealSellAstrict(Long memberId,BigDecimal money){
        BigDecimal value = getOtcDealPrice(SystemConfigConstants.OTC_DAY_DEAL_MEMBER_SELL);
        BigDecimal price = getMemberSellDealPriceDay(memberId);
        log.info("========已下单金额======{}",price);
        price = money.add(price);
        if(Objects.isNull(value)){
            return Boolean.FALSE;
        }
        if (price.compareTo(value)>0) {
            return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }
    /**
     * 用户每周承兑次数上限
     * @return
     */
    public Boolean isAcceptCount(Long memberId){
        String value = systemConfigService.getConfigValue(SystemConfigConstants.OTC_WEEK_COUNT);
        if(StringUtils.isEmpty(value)){
            return true;
        }
        //查询本周的卖出订单数量
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String start = formatter.format(LocalDateTime.of(LocalDate.now().minusDays(LocalDate.now().getDayOfWeek().getValue() - 1), LocalTime.MIN));
        String end = formatter.format(LocalDateTime.of(LocalDate.now().plusDays(8 - LocalDate.now().getDayOfWeek().getValue()), LocalTime.MAX));
        long count = orderDao.getWeekCount(memberId,start,end);
        return count+1<=Long.parseLong(value);
    }
    /**
     * 获取otc交易额限制配置
     * @return
     */
    public BigDecimal getOtcDealPrice(String configKey){
        String value = systemConfigService.getConfigValue(configKey);
        if(StringUtils.isEmpty(value)){
            return null;
        }
        return new BigDecimal(value);
    }

    /**
     * 判断用户是否有进行中的订单
     * @return
     */
    public Boolean isOrdering(Long memberId,Long customerId,AdvertiseType advertiseType){
        long count = orderDao.getOrdering(advertiseType.getOrdinal(),memberId,customerId);
        if(count>0){
            return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }

    /**
     * 查询承兑商昨日交易金额
     */
//    public List<BusinessMoneyVo> getBusinessPrice(AdvertiseType advertiseType) throws ParseException {
//        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
//        String start = formatter.format(LocalDateTime.of(LocalDate.now().minusDays(1), LocalTime.MIN));
//        String end = formatter.format(LocalDateTime.of(LocalDate.now().minusDays(1), LocalTime.MAX));
//        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//        return orderDao.getBusinessPrice(advertiseType,format.parse(start),format.parse(end));
//    }

    public List<String> getExchangeOrder(){
        return orderDao.getExchangeOrder();
    }


    /**
     * 是否禁止otc承兑、提现
     * @return
     */
    public boolean isForbidOtc(String phone,String email){
        String value = systemConfigService.getConfigValue(SystemConfigConstants.OTC_FORBID_PHONE);
        if(StringUtils.isEmpty(value)){
            return false;
        }
        String[] strings = value.split(",");
        List<String> list = Arrays.asList(strings);
        return list.contains(phone)||list.contains(email);
    }

    /**
     * 是否在交易时间内
     * @return
     */
    public void isForbidOtcTime() throws ServiceException {
        String value = systemConfigService.getConfigValue(SystemConfigConstants.OTC_FORBID_TIME);
        OtcTime otcTime = JSON.parseObject(value,OtcTime.class);
        boolean flag = isBetween(LocalDateTime.now().toLocalTime(), LocalTime.parse(otcTime.getStartTime()), LocalTime.parse(otcTime.getEndTime()));
        if(!flag){
            throw new ServiceException("不在otc开放时间内:"+otcTime.getStartTime()+"-"+otcTime.getEndTime());
        }
        //判断是否为工作日
        Integer week = LocalDateTime.now().getDayOfWeek().getValue();
        if(!otcTime.getWeek().contains(String.valueOf(week))){
            throw new ServiceException("otc仅周一至周五开放");
        }
    }

    public void isBeforeExchange(AdvertiseType advertiseType,Date time,String merPhone,String merEmail,String mobilePhone,String email) throws ServiceException {
        //如果在交易时间内，判断是否为大额承兑的账号，如果是，可提前交易的用户可以提前三分钟承兑
        List<String> list = getBeforeMember();
        AcceptAccount acceptAccount = getBeforeAccount();
        if(CollectionUtils.isEmpty(list)
                ||Objects.isNull(time)
                ||Objects.isNull(acceptAccount)
                ||advertiseType.equals(AdvertiseType.SELL)){
            return;
        }
        //如果不是配置中的账号，不提前交易
//        List<String> merPhones = Arrays.asList(acceptAccount.getPhones().split(","));
//        if(!merPhones.contains(merPhone)&&!merPhones.contains(merEmail)){
//            return;
//        }
        //上架时间+3分钟，如果在上架时间和十分钟之内，只能部分用户交易
        Calendar nowTime = Calendar.getInstance();
        nowTime.setTime(time);
        nowTime.add(Calendar.MINUTE, acceptAccount.getTime());
        Date date = new Date();
        //如果不在可提前交易用户中，不允许提前交易
        boolean mflag = date.after(time)&&date.before(nowTime.getTime());
        if(!mflag){
            return;
        }
        if(!list.contains(mobilePhone)&&!list.contains(email)){
            throw new ServiceException("otc交易繁忙，请稍后重试");
        }
    }

    /**
     * 可提前交易的用户
     *
     * @throws ServiceException
     */
    public List<String> getBeforeMember() {
        String value = systemConfigService.getConfigValue(SystemConfigConstants.OTC_FORBID_MEMBER);
        if(StringUtils.isEmpty(value)){
            return new ArrayList<>();
        }
        String[] strings = value.split(",");
        return Arrays.asList(strings);
    }

    /**
     * 提前承兑的大额账号
     *
     * @throws ServiceException
     */
    public AcceptAccount getBeforeAccount() {
        String value = systemConfigService.getConfigValue(SystemConfigConstants.OTC_FORBID_MERCHART);
        if(StringUtils.isNotEmpty(value)){
            return JSON.parseObject(value,AcceptAccount.class);
        }
        return null;
    }

    @Data
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    public static class AcceptAccount{
        private String phones;

        private Integer time;
    }


    private static <C extends Comparable<? super C>> boolean isBetween(C value, C start, C end) {
        return value.compareTo(start) >= 0 && value.compareTo(end) < 0;
    }

    @Data
    public static class OtcTime{
        private String startTime;
        private String endTime;
        private String week;
    }
}
